﻿using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Nature.Toolkit;
using System.Text.RegularExpressions;
using Nature.Numerics;

namespace Nature
{
    [DebuggerDisplay("Nel={Nel}, Nsp={Nsp}, Nre={Nre}")]
    public abstract class ModelObject
    {
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private Dictionary<string, int> m_speciesIndexs;
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private Dictionary<string, int> m_elementIndexs;
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private IIdealGasSpeciesThermoPotentials m_idealGasSpeciesThermoPotentials;
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private IIdealGasSpeciesMolecularTransportProperties m_idealGasSpeciesMolecularTransportProperties;
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private IIdealGasMixtureMolecularTransportProperties m_idealGasMixtureMolecularTransportProperties;
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private IChemicalEquilibriumCalculator m_idealGasChemicalEquilibriumCalculator;
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private IChemicalKinetics m_idealGasChemicalKinetics;

        private double[][] m_wilkeGroup1;
        private double[][] m_wilkeGroup2;
        private double[][] m_elspNormalized;

        private IObjectPool<double[]> m_speciesArraysPool;

        protected void TabulateIdealGasThermodynamicPotentials(double tmin, double tmax, double delta)
        {
            lock (this)
            {
                if (this.IdealGasSpeciesThermoPotentials is IdealGasSpeciesThermoPotentialsTabulated)
                {
                    return;
                }
                else
                {
                    m_idealGasSpeciesThermoPotentials = this.IdealGasSpeciesThermoPotentials.Tabulate(tmin, tmax, delta);
                }
            }
        }
        
        public int Nel { get; private set; }

        public int Nsp { get; private set; }

        public int Nre { get; private set; }

        public ReadOnlyArray<string> ElementIDs { get; private set; }

        public ReadOnlyArray<string> SpeciesIDs { get; private set; }

        public ReadOnlyArray<string> ReactionIDs { get; private set; }

        public ReadOnlyArray<double> ElementMolarMasses { get; private set; }

        public ReadOnlyArray<double> SpeciesMolarMasses { get; private set; }

        public ReadOnlyMatrix<double> SpelMatrix { get; private set; }

        public ReadOnlyMatrix<double> ElspMatrix { get; private set; }

        /// <summary>
        /// Each species row is 1-normalized
        /// </summary>
        /// <remarks>
        /// Used mostly in chemical thermodynamics solvers
        /// </remarks>
        public ReadOnlyMatrix<double> ElspNormalizedMatrix
        {
            get
            {
                if (m_elspNormalized == null)
                {
                    var spel = SpelMatrix.Data;
                    m_elspNormalized = Numerics.Utilities.CreateMatrix<double>(Nel, Nsp);
                    for (int iel = 0; iel < Nel; ++iel)
                    {
                        double[] spRow = m_elspNormalized[iel];
                        for (int isp = 0; isp < Nsp; ++isp)
                        {
                            spRow[isp] = spel[isp][iel];
                        }
                        Vector vector = new Vector(spRow);
                        vector.Devide(vector.Max());
                    }
                }
                return m_elspNormalized;
            }
        }

        public IIdealGasSpeciesThermoPotentials IdealGasSpeciesThermoPotentials 
        {
            get 
            {
                return m_idealGasSpeciesThermoPotentials ??
                    (m_idealGasSpeciesThermoPotentials = GetIIdealGasSpeciesThermoPotentials());
            }
        }

        public IIdealGasSpeciesMolecularTransportProperties IdealGasSpeciesMolecularTransportProperties
        {
            get 
            {
                if (m_idealGasSpeciesMolecularTransportProperties == null)
                {
                    lock (this)
                    {
                        return m_idealGasSpeciesMolecularTransportProperties ??
                                        (m_idealGasSpeciesMolecularTransportProperties = GetIdealGasSpeciesMolecularTransportProperties()); 
                    }
                }
                return m_idealGasSpeciesMolecularTransportProperties;
            }
        }

        public IIdealGasMixtureMolecularTransportProperties IdealGasMixtureMolecularTransportProperties
        {
            get 
            {
                if (m_idealGasMixtureMolecularTransportProperties == null)
                {
                    lock (this) 
                    {
                        return m_idealGasMixtureMolecularTransportProperties ??
                            (m_idealGasMixtureMolecularTransportProperties = GetIdealGasMixtureMolecularTransportProperties());
                    }
                }
                return m_idealGasMixtureMolecularTransportProperties;
            }
        }

        public IChemicalEquilibriumCalculator IdealGasChemicalEquilibriumCalculator
        {
            get
            {
                if (m_idealGasChemicalEquilibriumCalculator == null)
                {
                    lock (this)
                    {
                        return m_idealGasChemicalEquilibriumCalculator ??
                            (m_idealGasChemicalEquilibriumCalculator = GetIdealGasChemicalEquilibriumCalculator());
                    }
                }
                return m_idealGasChemicalEquilibriumCalculator;
            }
        }

        public IChemicalKinetics IdealGasChemicalKinetics
        {
            get 
            { 
                return m_idealGasChemicalKinetics ??
                    (m_idealGasChemicalKinetics = GetIdealGasChemicalKinetics());
            }
        }

        /// <summary>
        /// Wilke approximation: (w_j / w_k) ** 0.25
        /// </summary>
        public ReadOnlyMatrix<double> WilkeGroup1
        {
            get
            {
                if (m_wilkeGroup1 == null)
                {
                    lock (this)
                    {
                        if (m_wilkeGroup1 != null) { return m_wilkeGroup1; }
                        var w = SpeciesMolarMasses.Data;
                        m_wilkeGroup1 = new double[Nsp][];
                        for (int isp = 0; isp < Nsp; ++isp)
                        {
                            m_wilkeGroup1[isp] = new double[Nsp];
                            for (int jsp = 0; jsp < Nsp; ++jsp)
                            {
                                m_wilkeGroup1[isp][jsp] = Math.Pow(w[jsp] / w[isp], 1.0 / 4.0);
                            }
                        } 
                    }
                }
                return m_wilkeGroup1;
            }
        }

        /// <summary>
        /// Wilke approximation: SQRT( 8 * (1 + w_k / w_j) )
        /// </summary>
        public ReadOnlyMatrix<double> WilkeGroup2
        {
            get
            {
                if (m_wilkeGroup2 == null)
                {
                    lock (this)
                    {
                        if (m_wilkeGroup2 != null) { return m_wilkeGroup2; }
                        var w = SpeciesMolarMasses.Data;
                        m_wilkeGroup2 = new double[Nsp][];
                        for (int isp = 0; isp < Nsp; ++isp)
                        {
                            m_wilkeGroup2[isp] = new double[Nsp];
                            for (int jsp = 0; jsp < Nsp; ++jsp)
                            {
                                m_wilkeGroup2[isp][jsp] = Math.Sqrt(8.0 * (1.0 + w[isp] / w[jsp]));
                            }
                        } 
                    }
                }
                return m_wilkeGroup2;
            }
        }

        internal IObjectPool<double[]> SpeciesArraysPool { get { return m_speciesArraysPool; } }

        protected void Initialize()
        {
            ElementIDs = this.GetElementIDs();
            ElementMolarMasses = this.GetElementMolarMasses();
            SpeciesIDs = this.GetSpeciesIDs();
            SpeciesMolarMasses = this.GetSpeciesMolarMasses();            
            SpelMatrix = new ReadOnlyMatrix<double>(GetSpelMatrix());
            ReactionIDs = GetReactionIDs();
            m_elementIndexs = ElementIDs.ToDictionary(i => i, i => ElementIDs.IndexOf(i), StringComparer.InvariantCultureIgnoreCase);
            m_speciesIndexs = SpeciesIDs.ToDictionary(i => i, i => SpeciesIDs.IndexOf(i), StringComparer.InvariantCultureIgnoreCase);
            Nel = ElementIDs.Length;
            Nsp = SpeciesIDs.Length;
            Nre = ReactionIDs.Length;
            double[][] elsp = new double[Nel][];
            for (int iel = 0; iel < Nel; ++iel)
            {
                double[] row = elsp[iel] = new double[Nsp];
                for (int isp = 0; isp < Nsp; ++isp)
                {
                    row[isp] = SpelMatrix[isp][iel];
                }
            }
            ElspMatrix = elsp;

            m_speciesArraysPool = ObjectPool<double[]>.CreateObjectPool(() => new double[Nsp]);
        }

        public IdealGasMixture CreateIdealGasMixture(string mixtureDefinition)
        {
            IMixtureDefinition mix = ParseMixtureDefinition(mixtureDefinition).Single();
            return new IdealGasMixture(this, mix);
        }
      
        public IdealGasMixture CreateIdealGasMixture()
        {
            return new IdealGasMixture(this, new UnimixMixtureDefinition(SpeciesIDs, new Temperature(298.15), new Pressure(1d, PressureUnits.Atmosphere)));
        }

        public IdealGasMixture CreateIdealGasMixture(IMixtureDefinition mixtureDefinition)
        {
            var mixture = CreateIdealGasMixture();
            mixture.Update(mixtureDefinition);
            return mixture;
        }

        public IEnumerable<IdealGasMixture> CreateIdealGasMixtureCollection(string mixtureDefinition)
        {
            foreach (var mix in ParseMixtureDefinition(mixtureDefinition))
            {
                yield return new IdealGasMixture(this, mix);
            }
        }

        public string AdaptMixtureDefinition(string mixtureDefinition)
        {
            mixtureDefinition = mixtureDefinition.Trim();
            if (m_speciesIndexs.ContainsKey(mixtureDefinition))
            {
                mixtureDefinition = string.Format("*main{{{0} = 1}}", mixtureDefinition);
            }
            else if (false == Regex.IsMatch(mixtureDefinition, @"[{}]"))
            {
                mixtureDefinition = string.Format(@"*main{{
                    {0}
                }}", mixtureDefinition);
            }
            return mixtureDefinition;
        }

        public int GetSpeciesIndex(string speciesID, int defaultIndex = -1)
        {
            int index = 0;
            if (m_speciesIndexs.TryGetValue(speciesID, out index))
            {
                return index;
            }
            return defaultIndex;
        }

        public int GetElementIndex(string elementID, int defaultIndex = -1)
        {
            int index = 0;
            if (m_elementIndexs.TryGetValue(elementID, out index))
            {
                return index;
            }
            return defaultIndex;
        }

        #region Public Methods

        public double CalcMixtureMolarMassFromX(ReadOnlyArray<double> x)
        {
            var w = this.SpeciesMolarMasses;
            double sum = 0.0;
            for (int isp = 0; isp < Nsp; ++isp) { sum += x[isp] * w[isp]; }
            return sum;
        }

        public double CalcNormalizedMixtureMolarMassFromX(ReadOnlyArray<double> x)
        {
            var w = this.SpeciesMolarMasses;
            double wsum = 0.0;
            double xsum = 0.0;
            for (int isp = 0; isp < Nsp; ++isp)
            {
                wsum += x[isp] * w[isp];
                xsum += x[isp];
            }
            return wsum / xsum;
        }

        public void CalcElementAmountPerMass(double[] n, ReadOnlyArray<double> x)
        {
            var spel = this.SpelMatrix;
            double nsum = 0.0;
            double csum = 1.0 / CalcMixtureMolarMassFromX(x);

            using (var poolItem = m_speciesArraysPool.Acquire())
            {
                double[] conc = poolItem.Unwrap();
                for (int isp = 0; isp < Nsp; ++isp)
                {
                    conc[isp] = x[isp] * csum;
                }
                for (int iel = 0; iel < Nel; ++iel)
                {
                    n[iel] = 0.0;
                    for (int isp = 0; isp < Nsp; ++isp)
                    {
                        n[iel] += conc[isp] * spel[isp][iel];
                    }
                    nsum += n[iel];
                }
            }
        }

        #endregion
        
        protected abstract string[] GetElementIDs();
        protected abstract string[] GetSpeciesIDs();
        protected abstract string[] GetReactionIDs();      
        protected abstract double[] GetElementMolarMasses();
        protected abstract double[] GetSpeciesMolarMasses();
        protected abstract double[][] GetSpelMatrix();
        protected abstract IIdealGasSpeciesThermoPotentials GetIIdealGasSpeciesThermoPotentials();
        protected abstract IIdealGasSpeciesMolecularTransportProperties GetIdealGasSpeciesMolecularTransportProperties();
        protected abstract IIdealGasMixtureMolecularTransportProperties GetIdealGasMixtureMolecularTransportProperties();
        protected abstract IChemicalEquilibriumCalculator GetIdealGasChemicalEquilibriumCalculator();
        protected abstract IChemicalKinetics GetIdealGasChemicalKinetics();
        public abstract IEnumerable<IMixtureDefinition> ParseMixtureDefinition(string mixtureDefinition);

    }   
    
}
